// fast4j - Fast & Agile Service Tools for Java
// Copyright (C) 2007 Alexandre ROMAN
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

// $Id:Main.java 51 2007-04-08 12:37:12Z alexandre.roman $

package fast4j.ace.cli;

import static org.apache.commons.cli.OptionBuilder.withDescription;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.Parser;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import fast4j.ace.ACE;
import fast4j.ace.ApplicationCreationFailedException;
import fast4j.ace.ApplicationCreator;
import fast4j.ace.ApplicationDescriptor;
import fast4j.ace.ApplicationDescriptorReader;
import fast4j.ace.PropertyDescriptor;

/**
 * Main class for command-line interface.
 * @author alexandre.roman
 */
public class Main {
    private static final Logger LOG = LoggerFactory.getLogger(Main.class);
    
    /**
     * Main entry point.
     * @param args arguments (use <code>-h</code> to display help information)
     */
    public static void main(String[] args) {
        boolean parseError = false;
        
        final Parser parser = new GnuParser();
        final Options opts = buildOptions();
        CommandLine cmd = null;
        try {
            cmd = parser.parse(opts, args, true);
        } catch (ParseException e) {
            LOG.error("Failed to parse options: [ {} ]", e.getMessage());
            parseError = true;
        }
        
        if (cmd == null || cmd.hasOption('h')) {
            final PrintWriter out = new PrintWriter(System.out, true);
            final HelpFormatter hf = new HelpFormatter();
            out.println("Usage: ace <options>");
            out.println("Options:");
            hf.printOptions(out, 80, opts, 4, 4);
            out.close();
            System.exit(parseError ? 4 : 0);
        }
        
        if (cmd.hasOption('v')) {
            System.out.println("ACE version: " + ACE.getVersion());
            System.exit(0);
        }
        
        final List<File> extensionDirs = new ArrayList<File>(1);
        if (cmd.hasOption('e')) {
            final String dirs = StringUtils.trimToNull(cmd.getOptionValue('e'));
            if (dirs != null) {
                for (final StringTokenizer tokens = new StringTokenizer(dirs,
                        File.pathSeparator); tokens.hasMoreTokens();) {
                    final File extensionDir = new File(tokens.nextToken());
                    if (extensionDir.exists() && extensionDir.isDirectory()) {
                        extensionDirs.add(extensionDir);
                    }
                }
            }
        }
        
        if (!extensionDirs.isEmpty()) {
            LOG.info("Extension directories:");
            for (final File extensionDir : extensionDirs) {
                try {
                    LOG.info(" o {}", extensionDir.getCanonicalPath());
                } catch (IOException e) {
                    throw new IllegalStateException(
                            "Failed to access extension directory: "
                                    + extensionDir.getAbsolutePath());
                }
            }
        }
        
        final ClassLoader classLoader = ACE
                .createExtensionClassLoader(extensionDirs);
        if (cmd.hasOption('l')) {
            logCreatorList(classLoader);
            System.exit(0);
        }
        
        final File dir;
        if (cmd.hasOption('o')) {
            dir = new File(cmd.getOptionValue('o'));
        } else {
            dir = new File(System.getProperty("user.dir"));
        }
        LOG.info("Output directory: {}", dir.getAbsolutePath());
        
        final String creatorId;
        if (cmd.hasOption('c')) {
            creatorId = cmd.getOptionValue('c');
        } else {
            creatorId = "basic";
        }
        LOG.info("Using application creator: {}", creatorId);
        
        final ApplicationDescriptor descriptor;
        final File descriptorFile;
        if (cmd.hasOption('i')) {
            descriptorFile = new File(cmd.getOptionValue('i'));
        } else {
            descriptorFile = new File(creatorId + ".xml");
        }
        if (!descriptorFile.exists() || !descriptorFile.isFile()) {
            LOG.error("Failed to locate application descriptor: {}",
                descriptorFile.getPath());
            System.exit(3);
        }
        LOG.info("Using application descriptor: {}", descriptorFile
                .getAbsolutePath());
        
        try {
            descriptor = new ApplicationDescriptorReader()
                    .read(new FileInputStream(descriptorFile));
        } catch (IOException e) {
            LOG.error("Failed to read application descriptor: "
                    + e.getMessage(), e);
            System.exit(3);
            return;
        }
        
        final List<ApplicationCreator> creators = ACE
                .getApplicationCreators(classLoader);
        if (creators.isEmpty()) {
            LOG.error("No ApplicationCreator was found");
            System.exit(2);
        }
        
        ApplicationCreator creator = null;
        for (final Iterator<ApplicationCreator> i = creators.iterator(); i
                .hasNext()
                && creator == null;) {
            final ApplicationCreator candidate = i.next();
            if (creatorId.equals(candidate.getId())) {
                creator = candidate;
            }
        }
        if (creator == null) {
            LOG.error("Failed to load ApplicationCreator: {}", creatorId);
        }
        
        try {
            // start application creation
            creator.create(descriptor, dir);
        } catch (ApplicationCreationFailedException e) {
            LOG.error("Error", e);
            System.exit(1);
        }
    }
    
    /**
     * Display registered {@link ApplicationCreator} implementations.
     */
    private static void logCreatorList(ClassLoader classLoader) {
        final List<ApplicationCreator> creators = ACE
                .getApplicationCreators(classLoader);
        if (creators.isEmpty()) {
            LOG.error("No ApplicationCreator was found");
        } else {
            LOG.info("Found {} ApplicationCreator implementation(s)", creators
                    .size());
        }
        for (final ApplicationCreator creator : creators) {
            LOG.info(" o {}", creator.getId());
            if (creator.getName() != null) {
                LOG.info("   - Name:        {}", creator.getName());
            }
            if (creator.getDescription() != null) {
                LOG.info("   - Description: {}", creator.getDescription());
            }
            
            final List<PropertyDescriptor> props = new ArrayList<PropertyDescriptor>(
                    creator.getSupportedProperties());
            if (!props.isEmpty()) {
                Collections.sort(props);
                LOG.info("   - Properties:");
            }
            for (final PropertyDescriptor prop : props) {
                final StringBuilder buf = new StringBuilder("     - ")
                        .append(prop.getName());
                if (prop.getDescription() != null) {
                    buf.append(": ").append(prop.getDescription());
                }
                if (prop.getDefaultValue() != null) {
                    buf.append(" [").append(prop.getDefaultValue()).append("]");
                }
                LOG.info(buf.toString());
            }
        }
    }
    
    /**
     * Build command-line options.
     */
    @SuppressWarnings("static-access")
    private static Options buildOptions() {
        final Options opts = new Options();
        opts.addOption(withDescription("Display help information").withLongOpt(
            "help").create('h'));
        opts.addOption(withDescription("Display version information")
                .withLongOpt("version").create('v'));
        opts.addOption(withDescription("List available applications creators")
                .withLongOpt("list").create('l'));
        opts.addOption(withDescription("Output directory").withLongOpt(
            "output-directory").hasArg().withArgName("directory").create('o'));
        opts.addOption(withDescription("Application creator to use")
                .withLongOpt("creator").hasArg().withArgName("creator id")
                .create('c'));
        opts.addOption(withDescription("Input application descriptor")
                .withLongOpt("input-descriptor").hasArg().withArgName("file")
                .create('i'));
        opts.addOption(withDescription("Extension directory").withLongOpt(
            "extension-directory").hasArg().withArgName("directory")
                .create('e'));
        
        return opts;
    }
}
